package com.hydra.auth.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hydra.auth.common.SerialNoConstant;
import com.hydra.auth.dto.ModifyPassDto;
import com.hydra.auth.dto.RegisterDto;
import com.hydra.auth.entity.SysRole;
import com.hydra.auth.entity.SysUser;
import com.hydra.auth.repository.SysRoleService;
import com.hydra.auth.repository.SysUserService;
import com.hydra.auth.service.LoginService;
import com.hydra.auth.service.SerialNoService;
import com.hydra.auth.utils.AesEncryptUtil;
import com.hydra.auth.utils.KaptchaUtil;
import com.hydra.base.auth.UserRoleEnum;
import com.hydra.base.common.Constant;
import com.hydra.base.dto.auth.AuthUser;
import com.hydra.base.error.BusinessException;
import com.hydra.base.error.ValidateException;
import com.hydra.base.redis.RedisPlugin;
import com.hydra.base.redis.RedisService;
import com.hydra.base.resp.BaseResult;
import com.hydra.base.utils.*;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 用户接口实现类
 * @Author Hydra
 * @Date 2022/5/27 13:27
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private SysUserService sysUserRepository;

    @Resource
    private SysRoleService sysRoleRepository;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private SerialNoService serialNoService;

    @Resource
    private RedisService redisService;

    @Override
    @Transactional
    public SysUser register(RegisterDto data) {
        //判断用户名是否存在
        CommonUtil.requireNonNull(data.getUsername(), "用户名");//前端可以把手机号或者邮箱作为用户名上传

        //判断两次密码是否一致
        this.checkPasswordEquals(data.getPassword(), data.getPasswordAgain());

        //检验手机号,为空不校验
        String phone = data.getPhone();
        ValidateUtil.validatePhone(phone, "手机号码", true);

        //校验邮箱,为空不校验
        String email = data.getEmail();
        ValidateUtil.validateEmail(email, "电子邮箱", true);

        //分布式锁，主要用于获取用户名是否存在
        RLock lock = redissonClient.getFairLock(Constant.REDISSON_LOCK_USER_REGISTER);
        try {
            //锁一点时间，防止死锁
            lock.lock(10, TimeUnit.SECONDS);

            //检查账户是否存在
            this.checkAccount(data);

            //创建用户
            return this.doRegister(data);

        }finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 获取验证码
     * @param key
     * @return
     */
    @Override
    public BufferedImage getValidateCode(String key) {
        String validateCodeKey = this.getValidateCodeRedisKey(key);
        String kaptchaCode = KaptchaUtil.getKaptchaProducer().createText();
        redisService.set(validateCodeKey, kaptchaCode, 30, TimeUnit.MINUTES);
        return KaptchaUtil.getKaptchaProducer().createImage(kaptchaCode);
    }

    @Override
    public void validateCode(String validateCode, String key) {
        Assert.isTrue(StrUtil.isNotBlank(validateCode), "验证码不能为空");
        String validateCodeKey = this.getValidateCodeRedisKey(key);

        // 从缓存取出正确的验证码和用户输入的验证码比对
        String correctValidateCode = redisService.get(validateCodeKey);

        Assert.isTrue(StrUtil.isNotBlank(correctValidateCode),"验证码已过期");
        Assert.isTrue(validateCode.equals(correctValidateCode),"您输入的验证码不正确");

        // 验证码验证通过，删除 Redis 的验证码
        redisService.deleteKey(validateCodeKey);
    }

    private String getValidateCodeRedisKey(String key){
        return "AUTH:VERIFY_CODE:" + key;
    }

    /**检查账户是否存在*/
    private void checkAccount(RegisterDto data){
        String username = data.getUsername();
        String phone = data.getPhone();
        String email = data.getEmail();
        //校验用户名是否注册,必填，不能为空
        List<SysUser> users = sysUserRepository.findByUsername(username);
        if(!users.isEmpty()){
            throw new ValidateException("用户名已经存在！");
        }
        //校验邮箱是否注册,为空不校验
        if(StringUtil.isNotBlank(email)){
            users = sysUserRepository.findByEmail(email);
            if(!users.isEmpty()){
                throw new ValidateException("该邮箱已被注册！");
            }
        }
        //校验手机号是否注册,为空不校验
        if(StringUtil.isNotBlank(phone)){
            users = sysUserRepository.findByPhone(phone);
            if(!users.isEmpty()){
                throw new ValidateException("该手机号已被注册！");
            }
        }
    }

    /**创建用户*/
    private SysUser doRegister(RegisterDto data){
        //新建用户
        SysUser user = new SysUser();
        user.initEntity();
        BeanUtils.copyProperties(data, user);

        //发号器
        user.setCode(serialNoService.generateCode(SerialNoConstant.USER_CODE));

        user.setStatus(Constant.STATUS_ON);

        //处理新建用户的系统编码
        if(StringUtil.isBlank(user.getSystemCode())){
            user.setSystemCode(SpringUtils.getSystemCode());
        }

        //处理新建用户的注册平台
        if(StringUtil.isBlank(user.getPlatform())){
            user.setPlatform(SpringUtils.getPlatform());
        }

        //解密前端传过来的密码，做一次加密，然后传给数据库
        String password = user.getPassword();
        String realPassword = AesEncryptUtil.decrypt(password);
        String encodePassword = passwordEncoder.encode(realPassword);
        user.setPassword(encodePassword);
        sysUserRepository.saveOrUpdate(user);

        //保存角色信息
        List<SysRole> roleList = new ArrayList<>();
        roleList.add(new SysRole().setRoleCode(UserRoleEnum.ROLE_APP_USER.getCode()));
        sysRoleRepository.saveUserRoleInfo(user.getCode(), roleList);
        user.setRoles(roleList);

        //保存完之后重置入参的密码，方便后续做一次登录请求
        user.setPassword(password);

        return user;
    }

    /*@Override
    public SysUser getUserInfo() {
        SysUser principal = SecurityUtil.getPrincipal();
        if(principal != null){
            principal.setPassword(null);
            String username = principal.getUsername();
            SysUser user = sysUserRepository.findByAccount(username);
            if(user != null){
                user.setPassword(null);
                return user;
            }
        }
        return principal;
    }*/

    @Override
    public AuthUser getAuthUser() {
        return RequestUtils.getAuthUser();
    }

    /**
     * 修改密码
     */
    @Override
    public BaseResult<Boolean> modifyPassword(ModifyPassDto data) {

        //判断两次密码是否一致
        this.checkPasswordEquals(data.getPassword(), data.getPasswordAgain());

        //获取用户是否存在
        SysUser user = new SysUser()
                .setUsername(data.getUsername())
                .setEmail(data.getEmail())
                .setPhone(data.getPhone());
        SysUser realUser = this.findUser(user);
        if(realUser == null){
            throw new BusinessException("获取用户信息失败！");
        }

        //检验密码是否正确
        String realPassword = realUser.getPassword();
        String originPassword = AesEncryptUtil.decrypt(data.getOriginPassword());
        if(!passwordEncoder.matches(originPassword, realPassword)){
            throw new BusinessException("原密码错误！请检查后重试");
        }
        this.modifyPassword(realUser, data.getPassword());

        BaseResult<Boolean> result = new BaseResult<>();
        result.setResult(true);
        return result;
    }


    @Override
    public SysUser modifyPassword(SysUser user, String password){
        //修改密码
        String modifyPassword = AesEncryptUtil.decrypt(password);
        String encodePassword = passwordEncoder.encode(modifyPassword);
        user.setPassword(encodePassword);

        LambdaUpdateWrapper<SysUser> wrapper = Wrappers.<SysUser>lambdaUpdate()
                .set(SysUser::getPassword, encodePassword)
                .eq(SysUser::getId, user.getId());
        sysUserRepository.update(wrapper);
        return user;
    }

    @Override
    public SysUser findUser(SysUser user){
        if(user == null){
            throw new BusinessException("用户信息不能为空！");
        }
        SysUser result = this.findByUsername(user.getUsername());
        if(result != null){ return result; }

        result = this.findByPhone(user.getPhone());
        if(result != null){ return result; }

        result = this.findByEmail(user.getEmail());
        return result;
    }

    @Override
    public SysUser findByUsername(String target) {
        if(FormatUtil.isNotEmpty(target)){
            List<SysUser> list = sysUserRepository.findByUsername(target);
            if(!list.isEmpty()){
                return list.get(0);
            }
        }
        return null;
    }

    @Override
    public SysUser findByPhone(String target) {
        if(FormatUtil.isNotEmpty(target)){
            List<SysUser> list = sysUserRepository.findByPhone(target);
            if(!list.isEmpty()){
                return list.get(0);
            }
        }
        return null;
    }

    @Override
    public SysUser findByEmail(String target) {
        if(FormatUtil.isNotEmpty(target)){
            List<SysUser> list = sysUserRepository.findByEmail(target);
            if(!list.isEmpty()){
                return list.get(0);
            }
        }
        return null;
    }

    /**检查密码是否一致*/
    private void checkPasswordEquals(String password, String passwordAgain){
        //判断用户名是否存在
        CommonUtil.requireNonNull(password, "密码");
        CommonUtil.requireNonNull(passwordAgain, "密码");
        if(!password.equals(passwordAgain)){
            throw new ValidateException("两次密码不一致！");
        }
    }

}
